package com.changgou.search.service.impl;


import com.alibaba.fastjson.JSON;
import com.changgou.search.pojo.SkuInfo;
import com.changgou.search.service.SearchService;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.*;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class SearchServiceImpl implements SearchService {

    @Autowired
    ElasticsearchTemplate elasticsearchTemplate;

    @Override
    public Map search(Map<String, String> searchMap) {

        if(searchMap!=null){
            //创建 条件查询 封装对象
            NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();


// 条件查询------------------------------------------------------------------------------------------------------

            //创建布尔查询 (多条件查询) ; 将所有单条件的查询 放入 bool 查询中
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
            //关键字查询
            if(StringUtils.isNotEmpty(searchMap.get("keywords"))){  //获取前端传来的关键字是否不为空
                //不为空,然后创建 match(分词查询也叫模糊查询) 查询
                /**
                 * 参数1 : 要查询的 列名
                 * 参数2 : 前端传来的 关键字字段
                 * 参数3 : 字段分词查询是否需要同时符合所有分词; 相当于 && ;  Operator.OR 为符合一个分词即可;
                 */
                MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("name", searchMap.get("keywords")).operator(Operator.AND);
                //将该match单条件查询 放入 bool查询 中的 must (必须符合条件) 分组中,
                BoolQueryBuilder must = boolQueryBuilder.must(matchQueryBuilder);
            }

            //品牌查询
            if(StringUtils.isNotEmpty(searchMap.get("skuBrand"))){//获取前端传来的品牌是否不为空
                //不为空,然后创建 term(不分词查询,也叫精确查询) 查询
                TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("brandName", searchMap.get("skuBrand"));
                //将该term单条件查询 放入 bool查询 中的 filter (必须符合条件查询,但是比must效率高,因为他不计算 词条匹配得分) 分组中,
                boolQueryBuilder.filter(termQueryBuilder);
            }

            //规格查询
/*
            前端发来规格查询：http://localhost:9009/search?keywords=手机&spec_颜色=红色&spec_网络制式=电信3G&spec_版本=8G%2B256G
            后台接到数据后，可以根据前缀spec_来区分是否是规格，如果以 spec_xxx 开始的数据 则为规格数据，需要根据指定规格找信息。
*/
            Set<String> searchMapKey = searchMap.keySet();//获取 前端传来的参数的所有键值对的 键
            for (String key : searchMapKey) {  //循环遍利 , 查看那个Key中以 spec_xxx 开始
                if (key.startsWith("spec_")){
                    String value = searchMap.get(key).replace("%2B", "+");//因为 规格 中有特殊字符"+" 号(如:8G+128G),在网络传输的时候会编码,所以需要手动将"+" 解码
                    //ES 中 规格的 键名 : specMap.颜色.keyword 现在为 spec_颜色 需要转换
                    TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("specMap." + key.substring(5) + ".keyword",value);
                    boolQueryBuilder.filter(termQueryBuilder);
                }
            }

            //价格区间查询
            if(StringUtils.isNotEmpty(searchMap.get("price"))) {  //判断 传来的 价格参数是否为空
                String price = searchMap.get("price");  //获取 前端传来的 价格  :大概是 price=0‐500 或者 price= 500‐1000 依次类推，最后一个是 price=3000
                String[] split = price.split("-");  //根据 - 分割前端传来的参数 可以获得, 最小价格与最大价格
                if (split.length == 2) { //判断 传来的价格是否是两个 ,
                    RangeQueryBuilder price1 = QueryBuilders.rangeQuery("price").gte(split[0]).lte(split[1]);//如果是两个则设置最大值和最小值;
                    boolQueryBuilder.filter(price1);
                }
                    RangeQueryBuilder price1 = QueryBuilders.rangeQuery("price").gte(split[0]);//如果是一个的话则设置最小值
                    boolQueryBuilder.filter(price1);

                //封装查询条件
                nativeSearchQueryBuilder.withQuery(boolQueryBuilder);
            }



// 聚合查询-----------------------------------------------------------------------------------------------
            //品牌聚合查询,相当于根据品牌 分组;
            String skuBrand="skuBrand";
            /**
             * 参数1 , 聚合查询后的列名;
             * 参数2 , 根据那一列数据进行聚合查询
             */
            TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders.terms(skuBrand).field("brandName");
            nativeSearchQueryBuilder.addAggregation(termsAggregationBuilder);

            //规格聚合查询
            String skuSpec="skuSpec";
            TermsAggregationBuilder skuspecTermsAggregationBuilder = AggregationBuilders.terms("skuSpec").field("spec.keyword");
            nativeSearchQueryBuilder.addAggregation(skuspecTermsAggregationBuilder);


// 分页查询 ----------------------------------------------------------------------------------------------------
            //分页查询
            String pageNum = searchMap.get("pageNum");
            String pageSize = searchMap.get("pageSize");
            if(StringUtils.isEmpty(pageNum)){  //判断 传来的当前页是否为空, 如果为空则 设置默认值 1
                pageNum="1";
            }
            if(StringUtils.isEmpty(pageSize)){//判断 传来的每页显示条数是否为空, 如果为空则 设置默认值 30
                pageSize="30";
            }
            nativeSearchQueryBuilder.withPageable(PageRequest.of(Integer.parseInt(pageNum)-1, Integer.parseInt(pageSize)));


//数据排序---------------------------------------------------------------------------------------------------------
             //sortField : 根据那个域(列)进行排序
             //  sortRule : 获取 排序规则 , 升序或者降序
            if(StringUtils.isNotEmpty(searchMap.get("sortField"))&&StringUtils.isNotEmpty(searchMap.get("sortRule"))){//判断 该俩参数是否为空
                if("ASC".equals(searchMap.get("sortRule"))){  //判断 排序规则是什么 .ASC为 升序
                    //升序
                    nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(searchMap.get("sortField")).order(SortOrder.ASC));
                }else {
                    //降序
                    nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(searchMap.get("sortField")).order(SortOrder.DESC));
                }
            }

//高亮字段设置---------------------------------------------------------------------------------------------------*---
            //设置高亮域
            HighlightBuilder.Field field = new HighlightBuilder.Field("name");
            //前缀
            field.preTags("<span style='color:red'>");
            //后缀
            field.postTags("</span>");
            nativeSearchQueryBuilder.withHighlightFields(field);



            //封装查询结果 ;
            /**
             *参数1 :  构建 封装查询条件的对象
             * 参数2 : 要将结果 封装成的 实体类
             * 参数3 : 用于处理 查询出来的结果 类型转换;
             */
            AggregatedPage<SkuInfo> resultInfo = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class, new SearchResultMapper() {
                @Override
                /**
                 * 查询结果封装
                 */
                public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {

                    SearchHits hits = searchResponse.getHits();//获取查询结果
                    if(hits!=null){ //判断查询结果是否为空

                        ArrayList<T> skuinfoList = new ArrayList<>();  //创建一个 集合 用来存储 转换后的 SkuInfo商品对象

                        for (SearchHit hit : hits) {  //循环获取 每一条 商品对象
                            String SkuString = hit.getSourceAsString();  //获取商品对象数据 并转成String类型
                            SkuInfo skuInfo = JSON.parseObject(SkuString, SkuInfo.class);//将商品对象的 String类型 转成 SkuInfo对象;

                            //高亮字段设置

                            //获取高亮字段
                            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
                            if (highlightFields != null&&highlightFields.size()>0) {
                                skuInfo.setName(highlightFields.get("name").getFragments()[0].toString());
                            }
                            skuinfoList.add((T) skuInfo);

                        }
                        //构建返回对象
                        /**
                         * 参数1 为 结果对象的集合
                         * 参数3 为 结果数据的总数
                         */
                        return new AggregatedPageImpl<>(skuinfoList,pageable,hits.getTotalHits(),searchResponse.getAggregations());
                    }
                    return null;
                }
            });


            //结果对象的最后封装 当前结果数据类型为 AggregatedPage<SkuInfo>  转成 Map 返回;
            Map<String,Object> resultMap = new HashMap<>();//创建 用于封装结果的Map

            resultMap.put("pageNum", pageNum); // 当前页

            long totalElements = resultInfo.getTotalElements(); //总记录数

            resultMap.put("totalPages",resultInfo.getTotalPages());//总页数

            resultMap.put("rows", resultInfo.getContent());//数据集合



            // 品牌聚合数据 封装
            StringTerms brandTerms = (StringTerms) resultInfo.getAggregation(skuBrand);
            List<String> brandList = brandTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
            resultMap.put("brandList", brandList);

            // 规格聚合数据 封装
           StringTerms specTerms = (StringTerms) resultInfo.getAggregation(skuSpec);
            List<String> specList = specTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
            resultMap.put("specList", specList);
            return resultMap;
        }
        return null;


    }
}
